Skip to content
标签
内存
字数
1897 字
阅读时间
10 分钟

import com.commnetsoft.pub.monitor.memory.MemoryMonitorUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

/**
 * 内存监控,出现内存不够时输出相应日志
 * @author Brack.zhu
 * @date 2019/11/5
 */
@Component
public class MemoryMonitorRunner implements CommandLineRunner {

    private Logger log = LoggerFactory.getLogger(MemoryMonitorRunner.class);

    @Override
    public void run(String... args) throws Exception {
        log.info("JVM内存监听器启动中...");
        //Mbean 监听器 MemoryMonitorUtil.addUsageListener();
        //使用GC监听器监听JVM内存
        MemoryMonitorUtil.startGcUsageMonitor();
        log.info("JVM内存监听器完成.");
    }
}

CommandLineRunner接口是SpringBoot提供的一种简单的实现启动时执行某个任务的方案,添加一个model并实现CommandLineRunner接口,实现功能的代码放在实现的run方法中

java
package com.commnetsoft.pub.monitor.memory;

import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryPoolMXBean;
import java.util.List;

import javax.management.NotificationEmitter;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.commnetsoft.pub.monitor.memory.gc.GCFinalizeObject;
import com.commnetsoft.pub.monitor.memory.gc.GcNotificationHandler;
import com.commnetsoft.pub.monitor.memory.gc.UsageThresholdGcNotificationHandler;
import com.commnetsoft.pub.monitor.memory.threshold.MonitorListener;
import com.commnetsoft.pub.monitor.memory.threshold.NotificationHandler;
import com.commnetsoft.pub.monitor.memory.threshold.UsageThresholdNotificationHandler;

/**
 * 内存监控工具类,提供各内存块(Code Cache\Eden Space\Survivor Space\Tenured Gen)阀值监控和GC堆内存监控
 * 
 * 
 * Eden Space (heap) 内存最初从这个线程池分配给大部分对象。
 * 
 * Survivor Space (heap) 用于保存在eden space内存池中经过垃圾回收后没有被回收的对象。
 * 
 * Tenured Generation (heap) 用于保持已经在survivor space内存池中存在了一段时间的对象。
 * 
 * Permanent Generation (non-heap)
 * 保存虚拟机自己的静态(reflective)数据,例如类(class)和方法(method)对象。Java虚拟机共享这些类数据。
 * 这个区域被分割为只读的和只写的。 内存池“Perm Gen [shared-rw]”:
 * 包含所有虚拟机自身的反射数据(如类和方法对象)的内存池,类数据共享可读可写区。
 * 
 * 内存池“Perm Gen [shared-ro]”: 包含所有虚拟机自身的反射数据(如类和方法对象)的内存池,类数据共享只读区。
 * 
 * Code Cache (non-heap) HotSpot Java虚拟机包括一个用于编译和保存本地代码(native
 * code)的内存,叫做“代码缓存区”(code cache)。
 * 
 * 
 * @author Brack.zhu
 * @date 2017-3-7
 */
public class MemoryMonitorUtil {

	private static Logger log = LoggerFactory.getLogger(MemoryMonitorUtil.class);

	private static MonitorListener mmListener;

	/**
	 * 增加一个默认各内存池阀值监听器
	 * 
	 * @return
	 */
	public static boolean addUsageListener() {
		NotificationHandler notificationHandler = new UsageThresholdNotificationHandler();
		MonitorListener mListener = new MonitorListener(notificationHandler);
		return addUsageListener(mListener);
	}

	/**
	 * 增加各内存池阀值监听器
	 * 
	 * @param mListener
	 *            监听器
	 * @return
	 */
	public static boolean addUsageListener(MonitorListener mListener) {
		boolean result = false;
		try {
			MemoryMXBean mbean = ManagementFactory.getMemoryMXBean();
			NotificationEmitter emitter = (NotificationEmitter) mbean;
			List<MemoryPoolMXBean> poolMbean = ManagementFactory.getMemoryPoolMXBeans();
			for (MemoryPoolMXBean pool : poolMbean) {
				try {
					boolean rs=mListener.setUsageThreshold(pool);
					if(rs){
						emitter.addNotificationListener(mListener, null, pool);
					}
				} catch (Exception e) {
					log.error("内存池("+pool.getName()+":"+pool.getType()+")监听器增加异常:", e);
				}
			}
			MemoryMonitorUtil.mmListener = mListener;
			result = true;
		} catch (Exception e) {
			log.error("增加各内存池阀值监听器异常:", e);
		}
		return result;
	}

	/**
	 * 移除各内存池阀值监听器
	 * 
	 * @return
	 */
	public static boolean removeUsageListener() {
		boolean result = false;
		if (null != mmListener) {
			try {
				MemoryMXBean mbean = ManagementFactory.getMemoryMXBean();
				NotificationEmitter emitter = (NotificationEmitter) mbean;
				List<MemoryPoolMXBean> poolMbean = ManagementFactory.getMemoryPoolMXBeans();
				for (MemoryPoolMXBean pool : poolMbean) {
					try {
						boolean rs=mmListener.clearUsageThreshold(pool);
						if(rs){
							emitter.removeNotificationListener(mmListener, null, pool);	
						}
					} catch (Exception e) {
						log.error("内存池("+pool.getName()+":"+pool.getType()+")监听器移除异常:", e);
					}
				}
				mmListener = null;
				result = true;
			} catch (Exception e) {
				log.error("移除各内存池阀值监听器异常:", e);
			}
		}
		return result;
	}
	
	/**
	 * 启动GC堆内存监控,堆内存剩余20%触发Handler
	 * 
	 * @return
	 */
	public static boolean startGcUsageMonitor() {
		GcNotificationHandler gcHandler = new UsageThresholdGcNotificationHandler();
		return startGcUsageMonitor(20, gcHandler);
	}

	/**
	 * 启动GC堆内存监控
	 * 
	 * @param per
	 *            堆内存剩余百分比,达到这个值触发Handler;取值范围:1-99
	 * @return
	 */
	public static boolean startGcUsageMonitor(int per) {
		GcNotificationHandler gcHandler = new UsageThresholdGcNotificationHandler();
		return startGcUsageMonitor(per, gcHandler);
	}

	/**
	 * 启动GC堆内存监控
	 * 
	 * @param per
	 *            per 堆内存剩余百分比,达到这个值触发Handler;取值范围:1-99
	 * @param gcHandler
	 *            通知处理器
	 * @return
	 */
	public static boolean startGcUsageMonitor(int per, GcNotificationHandler gcHandler) {
		return GCFinalizeObject.start(per, gcHandler);
	}

	/**
	 * 停止GC堆内存监控
	 * 
	 * @return
	 */
	public static boolean stopGcUsageMonitor() {
		return GCFinalizeObject.stop();
	}	
}






package com.commnetsoft.pub.monitor.memory.threshold;

import java.lang.management.MemoryPoolMXBean;
import java.lang.management.MemoryUsage;
import java.text.DecimalFormat;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 内存池阀值通知处理类
 * 
 * @author Brack.zhu
 * @date 2017-3-7
 */
public class UsageThresholdNotificationHandler implements NotificationHandler {

	private DecimalFormat df = new DecimalFormat("0.00");

	private Logger log = LoggerFactory.getLogger(UsageThresholdNotificationHandler.class);

	@Override
	public void handleNotification(MemoryPoolMXBean memoryPool) {
		String name = memoryPool.getName();
		MemoryUsage mUsage = memoryPool.getUsage();
		long max = mUsage.getMax() / 1048576; // 1024 * 1024;
		long used = mUsage.getUsed() / 1048576; // 1024 * 1024;
		long free = max - used;
		float proportion = (float) free / (float) max;
		String proportionStr = df.format(proportion);
		log.warn("!警告!:(JVM)可用内存不足;可以设置更大内存-重启或者让研发检查;" + name + ":总量=" + max + "M,可用总量=" + free + "M,可用比例=" + proportionStr);
	}

}



package com.commnetsoft.pub.monitor.memory.threshold;

import java.lang.management.MemoryPoolMXBean;

/**
 * 内存池阀值通知处理接口
 * @author Brack.zhu
 * @date 2017-3-7
 */
public interface NotificationHandler {
	
	/**
	 * 处理通知
	 * @param memoryPool 内存池对象
	 */
	public void handleNotification(MemoryPoolMXBean memoryPool);
}



package com.commnetsoft.pub.monitor.memory.threshold;

import java.lang.management.MemoryPoolMXBean;
import java.lang.management.MemoryUsage;

import javax.management.Notification;
import javax.management.NotificationListener;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * 内存池阀值监听类
 * 
 * @author Brack.zhu
 * @date 2017-3-7
 */
public class MonitorListener implements NotificationListener {

	private NotificationHandler notificationHandler = null;

	private Logger log = LoggerFactory.getLogger(MonitorListener.class);

	public MonitorListener(NotificationHandler notificationHandler) {
		this.notificationHandler = notificationHandler;
	}

	/**
	 * 内存池阀值设置
	 * 
	 * @param mPool
	 * @return
	 */
	public boolean setUsageThreshold(MemoryPoolMXBean mPool) {
		boolean result=false;
		try {
			if (mPool.isUsageThresholdSupported() && "Tenured Gen".equalsIgnoreCase(mPool.getName())) {// 老年代
				MemoryUsage mUsage = mPool.getPeakUsage();
				long max = mUsage.getMax();
				long threshold = (long) (max * 0.8);//阀值为最大值的80%
				mPool.setUsageThreshold(threshold);
				result=true;
			}
		} catch (Exception e) {
			log.error("内存池(" + mPool.getName()+":"+mPool.getType() + ")阀值设置异常:", e);
		}
		return result;
	}

	/**
	 * 内存池阀值清除
	 * 
	 * @param mPool
	 * @return 
	 */
	public boolean clearUsageThreshold(MemoryPoolMXBean mPool) {
		boolean result=false;
		try {
			if (mPool.isUsageThresholdSupported()&& "Tenured Gen".equalsIgnoreCase(mPool.getName())) {// 老年代
				mPool.setUsageThreshold(0);// 设置为零,则将禁用使用量阈值超过检查。
				result=true;
			}
		} catch (Exception e) {
			log.error("内存池(" + mPool.getName()+":"+mPool.getType() + ")阀值清除异常:", e);
		}
		return result;
	}

	/**
	 * @param notification
	 * @param handback
	 *            MemoryPoolMXBean内存池对象
	 */
	@Override
	public void handleNotification(Notification notification, Object handback) {
		if (null != notificationHandler) {
			if (handback instanceof MemoryPoolMXBean) {
				MemoryPoolMXBean mPool = (MemoryPoolMXBean) handback;
				notificationHandler.handleNotification(mPool);
			}
		} else {
			log.error("notificationHandler 对象为空!");
		}
	}

}




package com.commnetsoft.pub.monitor.memory.gc;

import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * <pre>
 * GC调用后最后实现公用方法对象---可用内存与总内存的比例,可用低于0.2的情况进行日志输出
 *  使用方法:构造一次该对象即可,如下
 *  public static void main(){
 *      ....
 *      new GCFinalizeObject();
 *      ....
 *   }
 *  </pre>    
 * @author Black.zhu
 * @date 2012-7-12
 */
public class GCFinalizeObject {
	
	private static boolean runFlag=true;
	
	private static float gcUsageThreshold=0.20f;
	
	private static GcNotificationHandler gcUsageMonitorHandler;
	
	private static Logger log = LoggerFactory.getLogger(GCFinalizeObject.class);

	@Override
	protected void finalize() throws Throwable {
		try {
			if(null!=gcUsageMonitorHandler){
				MemoryMXBean memory=ManagementFactory.getMemoryMXBean();
				MemoryUsage usage=memory.getHeapMemoryUsage();
				long max=usage.getMax()/1048576; //1024 * 1024;
				long used=usage.getUsed()/1048576; //1024 * 1024;
				long free=max-used;
				float proportion = (float)free /(float) max;
				int compareRs = Float.compare(gcUsageThreshold, proportion);// 不能小于0.2
				if (compareRs > 0) {
					gcUsageMonitorHandler.handleNotification(max,free,proportion);
				}	
			}else{
				log.error("GcNotificationHandler 对象为空!");	
			}
		} catch (Exception e) {
			log.error("GCFinalizeObject finalize 异常:", e);
		}
		if(runFlag){
			new GCFinalizeObject();
		}
		super.finalize();
	}
	
	/**
	 * 启动GC堆内存监控
	 * @param per 堆内存剩余百分比,达到这个值触发Handler;取值范围:1-99
	 * @param handler 
	 * @return
	 */
	public static boolean start(int per,GcNotificationHandler handler){
		gcUsageMonitorHandler=handler;
		if(per<100&&per>=1){
			gcUsageThreshold=per/100.0F;
		}
		new GCFinalizeObject();
		return true;
	}
	
	/**
	 * 停止GC堆内存监控
	 * @return
	 */
	public static boolean stop(){
		runFlag=false;
		gcUsageMonitorHandler=null;
		return true;
	}
	
}



package com.commnetsoft.pub.monitor.memory.gc;


/**
 * GC堆内存监控通知处理接口
 * @author Brack.zhu
 * @date 2017-3-7
 */
public interface GcNotificationHandler {
	
	/**
	 * 通知处理
	 * @param max 最大堆内存
	 * @param free 可用堆内存
	 * @param proportion 可用堆内存比率
	 */
	public void handleNotification(long max,long free,float proportion);
	
}



package com.commnetsoft.pub.monitor.memory.gc;


import java.text.DecimalFormat;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * GC堆内存监控通知处理类
 * 
 * @author Brack.zhu
 * @date 2017-3-7
 */
public class UsageThresholdGcNotificationHandler implements GcNotificationHandler {

	private DecimalFormat df = new DecimalFormat("0.00");
	
	private long lastHandlerTime=0;
	
	private Logger log = LoggerFactory.getLogger(UsageThresholdGcNotificationHandler.class);

	@Override
	public void handleNotification(long max, long free, float proportion) {
		long now=System.currentTimeMillis();
		if((now-lastHandlerTime)>1500){//至少间隔1.5秒输出
			lastHandlerTime=now;
			String proportionStr = df.format(proportion);
			log.warn("!警告!:(JVM)可用内存不足;可以设置更大内存-重启或者让研发检查;总量=" + max + "M,可用总量=" + free + "M,可用比例=" + proportionStr);	
		}
	}

}